package com.gitee.empty_null.orm.converter;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.gitee.empty_null.orm.entity.RegisteredClientEntity;
import org.springframework.lang.NonNull;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author xuhainan
 * @date 2024/1/22 15:02
 * @region hefei
 */
public class RegisteredClientConverter implements DelegateConverter<RegisteredClient, RegisteredClientEntity> {

    private final PasswordEncoder passwordEncoder;

    public RegisteredClientConverter(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }


    @Override
    public RegisteredClientEntity convert(@NonNull RegisteredClient source) {
        RegisteredClientEntity entity = RegisteredClientEntity.create();
        entity.setId(source.getId());
        entity.setClientId(source.getClientId());
        entity.setClientIdIssuedAt(DateUtil.toLocalDateTime(source.getClientIdIssuedAt()));
        entity.setClientSecret(passwordEncoder.encode(source.getClientSecret()));
        entity.setClientSecretExpiresAt(DateUtil.toLocalDateTime(source.getClientSecretExpiresAt()));
        entity.setClientName(source.getClientName());

        String clientAuthenticationMethodValues = source.getClientAuthenticationMethods()
                .stream()
                .map(ClientAuthenticationMethod::getValue)
                .distinct()
                .collect(Collectors.joining(","));
        entity.setClientAuthenticationMethods(clientAuthenticationMethodValues);

        String authorizationGrantTypeValues = source.getAuthorizationGrantTypes()
                .stream()
                .map(AuthorizationGrantType::getValue)
                .distinct()
                .collect(Collectors.joining(","));
        entity.setAuthorizationGrantTypes(authorizationGrantTypeValues);

        entity.setRedirectUris(StringUtils.collectionToCommaDelimitedString(source.getRedirectUris()));
        entity.setPostLogoutRedirectUris(StringUtils.collectionToCommaDelimitedString(source.getPostLogoutRedirectUris()));
        entity.setScopes(StringUtils.collectionToCommaDelimitedString(source.getScopes()));
        entity.setClientSettings(toJsonStr(source.getClientSettings().getSettings()));
        entity.setTokenSettings(toJsonStr(source.getTokenSettings().getSettings()));
        return entity;
    }

    private String toJsonStr(Object obj) {
        JSONConfig config = JSONConfig.create().setIgnoreNullValue(false);
        return JSONUtil.toJsonStr(obj, config);
    }


    @Override
    public RegisteredClient transform(RegisteredClientEntity target) {
        Set<ClientAuthenticationMethod> clientAuthenticationMethods =
                StringUtils.commaDelimitedListToSet(target.getClientAuthenticationMethods())
                        .stream()
                        .map(RegisteredClientConverter::resolveClientAuthenticationMethod)
                        .collect(Collectors.toSet());

        Set<AuthorizationGrantType> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(target.getAuthorizationGrantTypes())
                .stream()
                .map(RegisteredClientConverter::resolveAuthorizationGrantType)
                .collect(Collectors.toSet());

        Set<String> redirectUris = StringUtils.commaDelimitedListToSet(target.getRedirectUris());
        Set<String> postLogoutRedirectUris = StringUtils.commaDelimitedListToSet(target.getPostLogoutRedirectUris());
        Set<String> clientScopes = StringUtils.commaDelimitedListToSet(target.getScopes());

        ClientSettings clientSettings = clientSettings(target.getClientSettings());
        TokenSettings tokenSettings = tokenSettings(target.getTokenSettings());


        return RegisteredClient.withId(target.getId())
                .clientId(target.getClientId())
                .clientIdIssuedAt(parseInstant(target.getClientIdIssuedAt()))
                .clientSecret(target.getClientSecret())
                .clientSecretExpiresAt(parseInstant(target.getClientSecretExpiresAt()))
                .clientName(target.getClientName())
                .clientAuthenticationMethods(authenticationMethods -> authenticationMethods.addAll(clientAuthenticationMethods))
                .authorizationGrantTypes(grantTypes -> grantTypes.addAll(authorizationGrantTypes))
                .redirectUris(uris -> uris.addAll(redirectUris))
                .postLogoutRedirectUris(uris -> uris.addAll(postLogoutRedirectUris))
                .scopes(scopes -> scopes.addAll(clientScopes))
                .clientSettings(clientSettings)
                .tokenSettings(tokenSettings)
                .build();
    }

    private Instant parseInstant(LocalDateTime dateTime) {
        return Optional.ofNullable(dateTime)
                .map(time -> time.atZone(ZoneId.systemDefault()).toInstant())
                .orElse(null);
    }

    private ClientSettings clientSettings(String clientSettings) {
        return ClientSettings.withSettings(JSONUtil.parseObj(clientSettings))
                .build();
    }

    private TokenSettings tokenSettings(String tokenSettings) {
        JSONObject tokenSettingsMap = JSONUtil.parseObj(tokenSettings);
        TokenSettings.Builder tokenSettingsBuilder = TokenSettings.withSettings(tokenSettingsMap);

        OAuth2TokenFormat auth2TokenFormat = getOAuth2TokenFormat(tokenSettingsMap);
        tokenSettingsBuilder.accessTokenFormat(auth2TokenFormat);

        Duration authorizationCodeTimeToLive = tokenSettingsMap.get(ConfigurationSettingNames.Token.AUTHORIZATION_CODE_TIME_TO_LIVE, Duration.class);
        if (Objects.nonNull(authorizationCodeTimeToLive)) {
            tokenSettingsBuilder.authorizationCodeTimeToLive(authorizationCodeTimeToLive);
        }

        Duration accessTokenTimeToLive = tokenSettingsMap.get(ConfigurationSettingNames.Token.ACCESS_TOKEN_TIME_TO_LIVE, Duration.class);
        if (Objects.nonNull(accessTokenTimeToLive)) {
            tokenSettingsBuilder.accessTokenTimeToLive(accessTokenTimeToLive);
        }

        Duration deviceCodeTimeToLive = tokenSettingsMap.get(ConfigurationSettingNames.Token.DEVICE_CODE_TIME_TO_LIVE, Duration.class);
        if (Objects.nonNull(deviceCodeTimeToLive)) {
            tokenSettingsBuilder.deviceCodeTimeToLive(deviceCodeTimeToLive);
        }

        Duration refreshTokenTimeToLive = tokenSettingsMap.get(ConfigurationSettingNames.Token.REFRESH_TOKEN_TIME_TO_LIVE, Duration.class);
        if (Objects.nonNull(refreshTokenTimeToLive)) {
            tokenSettingsBuilder.refreshTokenTimeToLive(refreshTokenTimeToLive);
        }

        SignatureAlgorithm signatureAlgorithm = tokenSettingsMap.get(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM, SignatureAlgorithm.class);
        if (Objects.nonNull(signatureAlgorithm)) {
            tokenSettingsBuilder.idTokenSignatureAlgorithm(signatureAlgorithm);
        }

        return tokenSettingsBuilder.build();
    }

    private OAuth2TokenFormat getOAuth2TokenFormat(JSONObject tokenSettingsMap) {
        String auth2TokenFormatValue = tokenSettingsMap.getJSONObject(ConfigurationSettingNames.Token.ACCESS_TOKEN_FORMAT)
                .getStr("value");
        OAuth2TokenFormat auth2TokenFormat;
        if (StringUtils.hasText(auth2TokenFormatValue)) {
            auth2TokenFormat = OAuth2TokenFormat.SELF_CONTAINED;
        } else {
            auth2TokenFormat = new OAuth2TokenFormat(auth2TokenFormatValue);
        }
        return auth2TokenFormat;
    }


    private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
        if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
            return AuthorizationGrantType.AUTHORIZATION_CODE;
        } else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
            return AuthorizationGrantType.CLIENT_CREDENTIALS;
        } else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
            return AuthorizationGrantType.REFRESH_TOKEN;
        }
        return new AuthorizationGrantType(authorizationGrantType);        // Custom authorization grant type
    }

    private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
        if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
            return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
        } else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
            return ClientAuthenticationMethod.CLIENT_SECRET_POST;
        } else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
            return ClientAuthenticationMethod.NONE;
        }
        return new ClientAuthenticationMethod(clientAuthenticationMethod);        // Custom client authentication method
    }
}
